﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Resources;
using System.Windows.Forms;

using Microsoft.WowAddonStudio.Project.Import.Folder;

namespace Microsoft.WowAddonStudio.Project.Import
{
    /// <summary>
    /// Main form of the WoW add-in import wizard.
    /// </summary>
    public partial class ImportWizardForm : Form
    {
        #region Private members

        private ImportWizardPane[] pages;
        private ImportWizardPane activePage;
        private IImportPlugin importPlugin;
        private ImportType importType;
        private readonly ResourceManager resourceManager;

        #endregion

        #region Lifecycle methods

        /// <summary>
        /// Creates an instance of a <see cref="ImportWizardForm"/> class, and
        /// sets the import type to none.
        /// </summary>
        public ImportWizardForm()
        {
            InitializeComponent();
            resourceManager = new ResourceManager(typeof (ImportWizardResources));
            CreatePages();
            importType = ImportType.None;
        }

        #endregion

        #region Public properties and member functions

        /// <summary>
        /// Gets the path to the project file that is generated during the import.
        /// </summary>
        public string ProjectFilePath { get; private set; }

        /// <summary>
        /// Gets the page that is used to set import parameters.
        /// In the current implementation it is located at position 2.
        /// </summary>
        public ImportWizardPane ImportPage
        {
            get { return pages[1]; }
        }

        /// <summary>
        /// Gets the target picker page of the input process.
        /// </summary>
        public TargetPickerWizardPane TargetPage
        {
            get { return pages[2] as TargetPickerWizardPane; }
        }

        /// <summary>
        /// Gets the last page of the input process.
        /// In the current implementation it is located at position 4.
        /// </summary>
        public FinishWizardPane FinishPage
        {
            get { return pages[3] as FinishWizardPane; }
        }

        /// <summary>
        /// Activates page with given index.
        /// </summary>
        /// <param name="pageIndex">Index of page to be activated.</param>
        public void SetActivePage(int pageIndex)
        {
            if (pageIndex < 0 || pageIndex >= pages.Length)
                throw new ArgumentOutOfRangeException("pageIndex");

            ImportWizardPane newPage = pages[pageIndex];
            ImportWizardPane oldActivePage = activePage;

            if (!pagePanel.Controls.Contains(newPage))
                pagePanel.Controls.Add(newPage);

            newPage.Visible = true;

            activePage = newPage;
            var e = new CancelEventArgs();
            newPage.OnSetActive(e);

            if (e.Cancel)
            {
                newPage.Visible = false;
                activePage = oldActivePage;
            }

            // Hide all of the other pages.
            foreach (ImportWizardPane page in pages)
            {
                if (page != activePage)
                    page.Visible = false;
            }
        }

        #endregion

        #region Private and internal member functions

        /// <summary>
        /// Creates the pages.
        /// </summary>
        private void CreatePages()
        {
            pages = new ImportWizardPane[4];
            pages[0] = new InitPane();
            pages[1] = new CustomFolderPickerWizardPane();
            pages[2] = new TargetPickerWizardPane();
            pages[3] = new FinishWizardPane();
        }

        /// <summary>
        /// Handles the Load event of the ImportWizardForm control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ImportWizardForm_Load(object sender, EventArgs e)
        {
            //Set captions from resource...
            Text = resourceManager.GetString("WizardCaption");
            backButton.Text = resourceManager.GetString("BackButton");
            nextButton.Text = resourceManager.GetString("NextButton");
            finishButton.Text = resourceManager.GetString("FinishButton");
            cancelButton.Text = resourceManager.GetString("CancelButton");

            //Setup first page
            if (pages.Length != 0)
            {
                ResizeToFit();
                SetActivePage(0);
            }
            else
            {
                SetWizardButtons(WizardButtons.None);
            }
        }

        /// <summary>
        /// Resizes to fit.
        /// </summary>
        private void ResizeToFit()
        {
            var maxPageSize = new Size(buttonPanel.Width, 0);

            foreach (ImportWizardPane page in pages)
            {
                if (page.Width > maxPageSize.Width)
                    maxPageSize.Width = page.Width;
                if (page.Height > maxPageSize.Height)
                    maxPageSize.Height = page.Height;
            }

            foreach (ImportWizardPane page in pages)
            {
                page.Size = maxPageSize;
            }

            Size extraSize = Size;
            extraSize -= pagePanel.Size;

            Size newSize = maxPageSize + extraSize;
            Size = newSize;
        }

        /// <summary>
        /// Gets the index of the active.
        /// </summary>
        /// <returns></returns>
        private int GetActiveIndex()
        {
            ImportWizardPane activePage = GetActivePage();

            for (int i = 0; i < pages.Length; ++i)
            {
                if (activePage == pages[i])
                    return i;
            }

            return -1;
        }

        /// <summary>
        /// Gets the active page.
        /// </summary>
        /// <returns></returns>
        private ImportWizardPane GetActivePage()
        {
            return activePage;
        }

        /// <summary>
        /// Sets the wizard buttons.
        /// </summary>
        /// <param name="buttons">The buttons.</param>
        internal void SetWizardButtons(WizardButtons buttons)
        {
            backButton.Enabled = ((buttons & WizardButtons.Back) != 0);

            if ((buttons & WizardButtons.Finish) != 0)
            {
                finishButton.Visible = true;
                finishButton.Enabled = true;

                nextButton.Visible = false;
                nextButton.Enabled = false;

                AcceptButton = finishButton;
            }
            else
            {
                finishButton.Visible = false;
                finishButton.Enabled = false;

                nextButton.Visible = true;
                nextButton.Enabled = ((buttons & WizardButtons.Next) != 0);

                AcceptButton = nextButton;
            }
        }

        /// <summary>
        /// Pres the change page.
        /// </summary>
        /// <param name="delta">The delta.</param>
        /// <returns></returns>
        private WizardPageEventArgs PreChangePage(int delta)
        {
            // Figure out which page is next.
            int activeIndex = GetActiveIndex();
            int nextIndex = activeIndex + delta;

            if (nextIndex < 0 || nextIndex >= pages.Length)
                nextIndex = activeIndex;

            CreateImportPage(nextIndex);

            if (importType == ImportType.FolderImport)
            {
                ValidateImportLocation(activeIndex, ref nextIndex);
                ValidateTargetLocation(activeIndex, ref nextIndex);
            }

            // Fill in the event args.
            ImportWizardPane newPage = pages[nextIndex];
            var e = new WizardPageEventArgs {NewPageIndex = nextIndex, Cancel = false};

            return e;
        }

        /// <summary>
        /// Validates the target location.
        /// </summary>
        /// <param name="activeIndex">Index of the active.</param>
        /// <param name="nextIndex">Index of the next.</param>
        private void ValidateTargetLocation(int activeIndex, ref int nextIndex)
        {
            if (nextIndex == pages.ToList().IndexOf(FinishPage))
            {
                var fi = ImportPage.Settings as FolderImportSettings;
                var ft = TargetPage.TargetFolder;

                switch (FolderImportSettingsValidator.ValidateTargetLocation(fi.SourceUri.LocalPath,
                                                                             ft))
                {
                    case ImportTargetValidationResult.ALREADYEXISTS:
                        string targetPath = Path.Combine(ft,
                                                         Path.GetFileNameWithoutExtension(
                                                             FolderImportSettingsValidator.GetFirstTocFilePath(fi.SourceUri.LocalPath)));
                        string warningMessage = String.Format(resourceManager.GetString("ExistingTargetDir"), targetPath);

                        DialogResult res = MessageBox.Show(warningMessage, "Warning",
                                                           MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);

                        switch (res)
                        {
                            case DialogResult.Yes:
                                DeleteDirectoryRecursively(targetPath);
                                break;
                            case DialogResult.No:
                            case DialogResult.Cancel:
                                nextIndex = activeIndex;
                                break;
                            case DialogResult.None:
                            case DialogResult.OK:
                            case DialogResult.Abort:
                            case DialogResult.Retry:
                            case DialogResult.Ignore:
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                        break;
                    case ImportTargetValidationResult.VALID:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }

        /// <summary>
        /// Deletes the directory recursively.
        /// </summary>
        /// <param name="directory">The directory.</param>
        private static void DeleteDirectoryRecursively(string directory)
        {
            if (Directory.Exists(directory))
            {
                foreach (var file in Directory.GetFiles(directory))
                {
                    File.Delete(file);
                }

                foreach (var subFolder in Directory.GetDirectories(directory))
                {
                    DeleteDirectoryRecursively(subFolder);
                }

                Directory.Delete(directory);
            }
        }

        /// <summary>
        /// Validates the import location.
        /// </summary>
        /// <param name="activeIndex">Index of the active.</param>
        /// <param name="nextIndex">Index of the next.</param>
        private void ValidateImportLocation(int activeIndex, ref int nextIndex)
        {
            if (nextIndex == pages.ToList().IndexOf(TargetPage))
            {
                var fi = ImportPage.Settings as FolderImportSettings;
                if (fi != null)
                {
                    switch (FolderImportSettingsValidator.ValidateImportLocation(fi.SourceUri.LocalPath))
                    {
                        case ImportLocationValidationResult.NOSOURCEDIR:
                            MessageBox.Show(resourceManager.GetString("NoSourceDirError"), "Error",
                                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                            nextIndex = activeIndex;
                            break;
                        case ImportLocationValidationResult.NOTOC:
                            MessageBox.Show(resourceManager.GetString("NoTocError"), "Error",
                                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                            nextIndex = activeIndex;
                            break;
                        case ImportLocationValidationResult.MULTIPLETOC:
                            MessageBox.Show(resourceManager.GetString("MultipleTocWarning"), "Warning",
                                            MessageBoxButtons.OK, MessageBoxIcon.Warning);
                            nextIndex = activeIndex;
                            break;
                        case ImportLocationValidationResult.VALID:
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
            }
        }

        /// <summary>
        /// Creates the import page.
        /// </summary>
        /// <param name="nextIndex">Index of the next.</param>
        private void CreateImportPage(int nextIndex)
        {
            if (nextIndex == 1)
            {
                importType = ImportType.FolderImport;
                importPlugin = ImportPluginFactory.CreateImportPlugin(importType);
                pages[1] = importPlugin.GetImportWizardPane();
            }
        }

        /// <summary>
        /// Posts the change page.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.WowAddonStudio.Project.Import.WizardPageEventArgs"/> instance containing the event data.</param>
        private void PostChangePage(WizardPageEventArgs e)
        {
            if (!e.Cancel)
                SetActivePage(e.NewPageIndex);
        }

        /// <summary>
        /// Presses the button.
        /// </summary>
        /// <param name="buttons">The buttons.</param>
        internal void PressButton(WizardButtons buttons)
        {
            if ((buttons & WizardButtons.Finish) == WizardButtons.Finish)
                finishButton.PerformClick();
            else if ((buttons & WizardButtons.Next) == WizardButtons.Next)
                nextButton.PerformClick();
            else if ((buttons & WizardButtons.Back) == WizardButtons.Back)
                backButton.PerformClick();
        }

        /// <summary>
        /// Enables the cancel button.
        /// </summary>
        /// <param name="enableCancelButton">if set to <c>true</c> [enable cancel button].</param>
        internal void EnableCancelButton(bool enableCancelButton)
        {
            cancelButton.Enabled = enableCancelButton;
        }

        /// <summary>
        /// Handles the Closing event of the WizardSheet control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void WizardSheet_Closing(object sender, CancelEventArgs e)
        {
            if (!cancelButton.Enabled)
                e.Cancel = true;
            else if (!finishButton.Enabled)
                OnQueryCancel(e);
        }

        /// <summary>
        /// Raises the <see cref="E:QueryCancel"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        protected virtual void OnQueryCancel(CancelEventArgs e)
        {
            activePage.OnQueryCancel(e);
        }

        /// <summary>
        /// Handles the Click event of the cancelButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void cancelButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        /// <summary>
        /// Handles the Click event of the nextButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void nextButton_Click(object sender, EventArgs e)
        {
            WizardPageEventArgs wpea = PreChangePage(+1);
            activePage.OnWizardNext(wpea);
            PostChangePage(wpea);
        }

        /// <summary>
        /// Handles the Click event of the backButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void backButton_Click(object sender, EventArgs e)
        {
            WizardPageEventArgs wpea = PreChangePage(-1);
            activePage.OnWizardBack(wpea);
            PostChangePage(wpea);
        }

        /// <summary>
        /// Handles the Click event of the finishButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void finishButton_Click(object sender, EventArgs e)
        {
            try
            {
                var process = new ImportProcess(importPlugin, ImportPage.Settings, TargetPage.TargetFolder);
                process.ProgressChanged += process_ProgressChanged;
                process.RunWorkerCompleted += process_RunWorkerCompleted;
                EnableCancelButton(false);
                process.RunWorkerAsync();
            }
            catch (Exception ex)
            {
                var res = new ResourceManager(typeof (ImportWizardResources));
                MessageBox.Show(
                    String.Format(res.GetString("ImportErrorMessage"), ex.Message));
                EnableCancelButton(true);
            }
        }

        /// <summary>
        /// Handles the RunWorkerCompleted event of the process control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
        private void process_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(
                    String.Format(resourceManager.GetString("ImportErrorMessage"), e.Error.Message),
                    "Error during import",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                FinishPage.ResetProgress();
                EnableCancelButton(true);
            }
            else
            {
                ProjectFilePath = e.Result as string;
                var cea = new CancelEventArgs();
                activePage.OnWizardFinish(cea);
                if (cea.Cancel)
                    return;

                DialogResult = DialogResult.OK;
                Close();
            }
        }

        /// <summary>
        /// Handles the ProgressChanged event of the process control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/> instance containing the event data.</param>
        private void process_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            FinishPage.ReportProgress(e.ProgressPercentage);
        }

        #endregion

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if (e.KeyCode == Keys.Escape)
                this.Close();
        }
    }

    /// <summary>
    /// Flags for possible wizard buttons. The used
    /// buttons are back, next, finish, and cancel.
    /// </summary>
    [Flags]
    public enum WizardButtons
    {
        None = 0x0000,
        Back = 0x0001,
        Next = 0x0002,
        Finish = 0x0004,
    }
}